package test.base.medium.ffmpeg;

import static org.bytedeco.ffmpeg.global.avcodec.*;
import static org.bytedeco.ffmpeg.global.avformat.*;
import static org.bytedeco.ffmpeg.global.avutil.*;
import static org.bytedeco.ffmpeg.global.swscale.SWS_BILINEAR;
import static org.bytedeco.ffmpeg.global.swscale.sws_freeContext;
import static org.bytedeco.ffmpeg.global.swscale.sws_getContext;
import static org.bytedeco.ffmpeg.global.swscale.sws_scale;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.Map.Entry;

import org.bytedeco.ffmpeg.avcodec.AVCodec;
import org.bytedeco.ffmpeg.avcodec.AVCodecContext;
import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.ffmpeg.avformat.AVFormatContext;
import org.bytedeco.ffmpeg.avformat.AVIOContext;
import org.bytedeco.ffmpeg.avformat.AVOutputFormat;
import org.bytedeco.ffmpeg.avformat.AVStream;
import org.bytedeco.ffmpeg.avformat.Seek_Pointer_long_int;
import org.bytedeco.ffmpeg.avformat.Write_packet_Pointer_BytePointer_int;
import org.bytedeco.ffmpeg.avutil.AVDictionary;
import org.bytedeco.ffmpeg.avutil.AVFrame;
import org.bytedeco.ffmpeg.avutil.AVRational;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.ffmpeg.swscale.SwsContext;
import org.bytedeco.javacpp.BytePointer;
import org.bytedeco.javacpp.DoublePointer;
import org.bytedeco.javacpp.Pointer;
import org.bytedeco.javacpp.PointerPointer;
import org.bytedeco.javacv.Seekable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//import org.bytedeco.javacv.FFmpegFrameRecorder.SeekCallback;
//import org.bytedeco.javacv.FFmpegFrameRecorder.WriteCallback;
import org.bytedeco.javacv.FrameRecorder.Exception;

import hyl.core.MyFun;
import hyl.core.run.IFilter;
import hyl.core.run.MyRun;

public class RtspGrabber {
	
	 static final Logger log = LoggerFactory.getLogger("RtspGrabber");
	// 输入设备 视频文件或视频流上下文
	AVFormatContext 输入引擎 = null;

	// 解码器的上下文,除了解码器,还有解码时候需要用到的其他额外信息
	AVCodecContext 视频编码器 = null;
	AVCodecContext 音频解码器 = null;
	// 图像变形器
	SwsContext sws_ctx = null;
	// 解码器
	AVCodec 视频编码参数 = null;
	AVCodec 音频编码参数 = null;
	// 输入流

	AVDictionary 视频解码器配置项 = new AVDictionary(null);
	AVDictionary 音频解码器配置项 = new AVDictionary(null);
	AVStream 视频流 = null;
	AVStream 音频流 = null;
	// 视频流所在通道
	int 视频流位置 = -1;
	int 音频流位置 = -1;

	int 图像输出格式 = AV_PIX_FMT_BGR24;
	int _视频间隔帧 = 1;
	// packet 用于存放从流中取到的帧
	AVPacket packet = null;
	// pFrame 用于存放avpacket 解码后的 avframe
	AVFrame 视频frame = null;// 用c 声明 分配帧内存
	// pFrameRGB 存放解码帧变换输出的 RGB格式帧
	AVFrame 视频RGB = null;// 用c 声明 分配帧内存
	// packet 用于存放从流中取到的帧
	AVPacket 音频packet = null;
	// pFrame 用于存放avpacket 解码后的 avframe
	AVFrame 音频frame = null;// 用c 声明 分配帧内存
	String rtspurl;
	IFilter<AVFrame> fl图像过滤器 = null;

	IFilter<AVFrame> fl音频过滤器 = null;

	IFilter<AVPacket> av包过滤器 = null;

	public RtspGrabber(final String 打开路径) {
		rtspurl = 打开路径;
	}
	public AVFormatContext get引擎() {
		return 输入引擎;
	}

	/**
	 * 只能一个过滤器,多个会导致图像处理时间太久,影响视频采集时间同步
	 * 
	 * @param 过滤器
	 * @return
	 */
	public RtspGrabber load图像过滤器(IFilter<AVFrame> 过滤器) {
		fl图像过滤器 = 过滤器;
		return this;
	}

	/**
	 * 只能一个过滤器,多个会导致图像处理时间太久,影响视频采集时间同步
	 * 
	 * @param 过滤器
	 * @return
	 */
	public RtspGrabber load音频过滤器(IFilter<AVFrame> 过滤器) {
		fl音频过滤器 = 过滤器;
		return this;
	}

	/**
	 * 只能一个过滤器,多个会导致包处理时间太久,影响视频采集时间同步
	 * 
	 * @param 包过滤器
	 * @return
	 */
	public RtspGrabber load包过滤器(IFilter<AVPacket> 包过滤器) {
		av包过滤器 = 包过滤器;
		return this;
	}

	public RtspGrabber load视频解码配置项(Map<String, String> 自定义属性) {
		if (自定义属性 == null || 自定义属性.isEmpty())
			return this;
		for (Entry<String, String> e : 自定义属性.entrySet()) {
			av_dict_set(视频解码器配置项, e.getKey(), e.getValue(), 0);
		}
		return this;
	}

	public RtspGrabber load音频解码配置项(Map<String, String> 自定义属性) {
		if (自定义属性 == null || 自定义属性.isEmpty())
			return this;
		for (Entry<String, String> e : 自定义属性.entrySet()) {
			av_dict_set(音频解码器配置项, e.getKey(), e.getValue(), 0);
		}
		return this;
	}

	public void ini(	int 图像输出格式, int 间隔帧, int 输出宽, int 输出高) {
			if (图像输出格式>0)
				this.图像输出格式 = 图像输出格式;			
			try {
				step1();
				step2_视频(间隔帧, 输出宽, 输出高);
				step2_音频();			
			} catch (Exception e) {
				e.printStackTrace();
				release();
			}
		

	//		采集线程池.execute(() -> {});
	}
	public void start()	 {	
		try {		
			step3();
		} catch (Exception e) {
			e.printStackTrace();
		}
		release();
		

//		采集线程池.execute(() -> {});
}
	/**
	 * 采集
	 * 
	 * @return
	 * @throws Exception 
	 * @throws IOException
	 */
	void step1() throws Exception {

		输入引擎 = new AVFormatContext(null);
		// int 输入格式 = AV_PIX_FMT_YUVJ420P;

		// ssl时才需要开启网络解析
		// avformat_network_init();
		// 读取视频文件
		// 主要就是根据url创建InputStream，并且会根据不同协议（rtmp/rtsp/hls/文件等）尝试读取一些文件头数据（流媒体头数据）。

		if (avformat_open_input(输入引擎, rtspurl, null, null) != 0) {
			error( "无法打开" + rtspurl); // Couldn't open file

		}
		// 查找视频信息
		if (avformat_find_stream_info(输入引擎, (PointerPointer<Pointer>) null) < 0) {
			error( "无法发现视频流信息"); // Couldn't find stream information
		}
		/*
		 * 打印有关输入或输出格式的详细信息，例如<br> 持续时间，比特率，流，容器，程序，元数据，边数据，<br> 编解码器和时基。
		 */
		av_dump_format(输入引擎, 0, rtspurl, 0);// 将有关文件的信息转储到标准错误中

		for (int i = 0; i < 输入引擎.nb_streams(); i++) {
			AVStream 输入流 = 输入引擎.streams(i);
			// stream.codecpar().codec_id();
			// stream.codecpar().codec_type()
			if (输入流.codecpar().codec_type() == AVMEDIA_TYPE_VIDEO) {
				视频流位置 = i;// 视频流所在索引
				视频流 = 输入流;

			} else if (输入流.codecpar().codec_type() == AVMEDIA_TYPE_AUDIO) {
				音频流位置 = i;// 视频流所在索引
				音频流 = 输入流;
			}
		}
		if (视频流位置==-1&&音频流位置==-1) error("缺少音频视频文件");
		// packet 用于存放从流中取到的帧
		this.packet = new AVPacket();
		
	}

	void step2_视频( int 间隔帧, int 输出宽, int 输出高)  {
		_视频间隔帧 = 间隔帧 > 0 ? 间隔帧 : 1;
		if (视频流位置 == -1) {
			log.info(   "找不到关键帧,在" + rtspurl);
			return;
		}

		int 视频编码 = 视频流.codecpar().codec_id();
		// 查找解码器
		视频编码参数 = avcodec_find_decoder(视频编码);
		if (视频编码参数 == null) {
			log.info(  "缺少解码器:编码=" + 视频编码);
			return;
		}

		// 从解码器获取解码器对象
		视频编码器 = avcodec_alloc_context3(视频编码参数);// 需要使用avcodec_free_context释放
		// 事实上codecpar包含了大部分解码器相关的信息，这里是直接从AVCodecParameters复制到AVCodecContext
		avcodec_parameters_to_context(视频编码器, 视频流.codecpar());
		// av_codec_set_pkt_timebase(pCodecCtx, stream.time_base);

		// 打开解码器
		if (avcodec_open2(视频编码器, 视频编码参数, 视频解码器配置项) < 0) {
			log.info(  "无法打开解码器:编码=" + 视频编码);
			return;
		}
	
		// pFrame 用于存放avpacket 解码后的 avframe
		视频frame = av_frame_alloc();// 用c 声明 分配帧内存
		// pFrameRGB 存放解码帧变换输出的 RGB格式帧
		视频RGB = av_frame_alloc();// 用c 声明 分配帧内存

		/*
		 * 分配一个AVFrame并将其字段设置为默认值。<br>
		 * 
		 * 结果必须使用av_frame_free（）释放结构。<br>
		 * 
		 * @返回一个用默认值填充的AVFrame或失败时为NULL。<br>
		 * 
		 * 注意这只分配AVFrame本身，而不分配数据缓冲区。<br>
		 * 
		 * 数据缓冲区通过使用av_frame_get_buffer（）或其他方式分配<br>
		 * 
		 * 
		 */

		if (视频RGB == null) {
			log.info(  "无法分配avframe");
			return;
		}
		int 宽 = 视频编码器.width(), 高 = 视频编码器.height();
		视频RGB.width(宽);
		视频RGB.height(高);
		视频RGB.format(图像输出格式);

		// av_image_get_buffer_size 函数的作用是通过指定像素格式、图像宽、图像高来计算所需的内存大小
		// 旧的写法 numBytes = avpicture_get_size(图像格式, width, height);
		int numBytes = av_image_get_buffer_size(图像输出格式, 宽, 高, 1);
		System.out.println("numBytes"+numBytes);
		// 确定图像所需的缓冲区大小, 并分配缓冲区
		/*
		 * 分配一个适合所有内存访问的对齐内存块（包括CPU上可用的向量）。
		 */
		BytePointer buffer = new BytePointer(av_malloc(numBytes));
		// 将缓冲区的适当部分分配给pFrameRGB中的图像平面,把buffer 分配给pFrameRGB.data()
		// 请注意，pFrameRGB是一个AVFrame，但AVFrame是一个AVPicture的超集
		// 过期 avpicture_fill(new AVPicture(pFrameRGB), buffer, 图像格式,
		// pCodecCtx.width(),pCodecCtx.height());
		avutil.av_image_fill_arrays(视频RGB.data(), 视频RGB.linesize(), buffer, 图像输出格式, 宽, 高, 1);
		/*
		 * SwsContext sws_ctx 图像变换器 ,你需要它来表演缩放等操作
		 * 
		 * sws_getContext () 分配并返回一个SwsContext。
		 * 
		 * 使用sws_scale（）进行缩放/转换操作。
		 */
		sws_ctx = sws_getContext(宽, 高, 视频编码器.pix_fmt(), 宽, 高, 图像输出格式, SWS_BILINEAR, null, null, (DoublePointer) null);
		// 新建canvasFrame窗口，命名
		// CanvasFrame canvasFrame = new CanvasFrame("测试点击");
		// // 关闭窗口则退出程序
		// canvasFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		// canvasFrame.setAlwaysOnTop(true);
		// canvasFrame.showImage(converter.convert(mat));
		// ImageFrame iframe = new ImageFrame(会话id, 输出宽, 输出高);

		
	}

	void step2_音频() {

		if (音频流位置 == -1) {
			log.info( "找不到音频关键帧,在" + rtspurl);
			return;
		}

		int 音频编码 = 音频流.codecpar().codec_id();
		// 查找解码器
		音频编码参数 = avcodec_find_decoder(音频编码);
		if (音频编码参数 == null) {
			log.info( "缺少解码器:编码=" + 音频编码);
			return;
		}

		// 从解码器获取解码器对象
		音频解码器 = avcodec_alloc_context3(音频编码参数);// 需要使用avcodec_free_context释放
		// 事实上codecpar包含了大部分解码器相关的信息，这里是直接从AVCodecParameters复制到AVCodecContext
		avcodec_parameters_to_context(音频解码器, 音频流.codecpar());
		// av_codec_set_pkt_timebase(pCodecCtx, stream.time_base);

		// 打开解码器
		if (avcodec_open2(音频解码器, 音频编码参数, 音频解码器配置项) < 0) {
			log.info( "无法打开音频解码器:编码=" + 音频编码);
			return;
		}

		// pFrame 用于存放avpacket 解码后的 avframe
		音频frame = av_frame_alloc();// 用c 声明 分配帧内存

	
	}


	/**
	 * 开始遍历包
	 * @throws Exception 
	 */
	public void step3() throws Exception {
		
		int j = 0;// 视频抓帧索引位置
		boolean enable图像过滤 = true;
		boolean enable音频过滤 = true;
		boolean enable包过滤 = true;
		is运行 = true;
		// 读视频帧一个帧
		while (is运行 && av_read_frame(输入引擎, packet) >= 0) {
			AVPacket pkt=new AVPacket();
			av_packet_ref(pkt,packet);
			int idx = packet.stream_index();
			// 如果是图像关键帧帧
			if (idx == 视频流位置 && enable图像过滤 && fl图像过滤器 != null) {
				
				//System.out.println("d2_1_1");
				// 时间基转换
				AVRational time_base = av_inv_q(视频编码器.framerate());
				//av_packet_rescale_ts(packet, 视频流.time_base(), time_base);

				// 解码视频帧
				// 将AVPKT大小的视频帧从AVPKT>数据解码为图片。
				//// 一些解码器可以支持单个av包中的多个帧，例如解码器将只解码第一帧。
				// 函数 avcodec_decode_video2 已经过时了，现在推荐使用的是 avcodec_send_packet() 和
				// avcodec_receive_frame()，
				// 而在新版本中，avcodec_decode_video2 也是通过这两个新的 API 完成解码功能的。
				// 读取到一帧音频或者视频 packet
				int re = avcodec_send_packet(视频编码器, packet);
				if (re != 0)
					break;
				while (avcodec_receive_frame(视频编码器, 视频frame) == 0) {
					//System.out.println("d3_1_1");
					// 处理解码后音视频 pFrame
					sws_scale(sws_ctx, 视频frame.data(), 视频frame.linesize(), 0, 视频frame.height(), 视频RGB.data(),
							视频RGB.linesize());
					//System.out.println("d3_1_2");
		
					// 必须在里面处理因为 packet 是压缩模式,必须转成视频frame 以后才能识别
					// 另外根据i ,b,p帧 解压原理必须连续解压才可以识别
					if (j == 0) {
						if (fl图像过滤器.where(视频RGB)) {						
							enable图像过滤 = false;// 终止图像过滤
						}
					//	System.out.println("d4_1_1");
					}
					j++;
					if (j == _视频间隔帧) {
						j = 0;
					}
				}
				//System.out.println("d2_1_2");
			} 
			else if (idx == 音频流位置 && enable音频过滤 && fl音频过滤器 != null) {
			//	System.out.println("d2_2_1");
				// AVRational time_base = av_inv_q(视频编码器.framerate());
				// av_packet_rescale_ts(packet, 视频流.time_base(), time_base);
				int re = avcodec_send_packet(音频解码器, packet);
				if (re != 0)
					break;
				while (avcodec_receive_frame(音频解码器, 音频frame) == 0) {
//					sws_scale(sws_ctx, 音频frame.data(), 音频frame.linesize(), 0, 音频frame.height(), 视频RGB.data(),
//							视频RGB.linesize());
					//System.out.println("d3_2_1");
					// 音频处理部分
					enable音频过滤 = false;
					if (fl音频过滤器.where(音频frame)) {
						//System.out.println("d4_2_1");
						enable图像过滤 = false;// 终止图像过滤
					}
					
				}
				//System.out.println("d2_2_2");
			}
			
			//System.out.println("d1");
			if ( enable包过滤 && av包过滤器 != null && av包过滤器.where(pkt)) {
				System.out.println("包过滤");
				enable包过滤 = false;
			}
			
			//System.out.println("d1_2");
			av_packet_unref(packet);
			av_packet_unref(pkt);
		}
	}

	boolean is运行 = false;
	public void error(String 异常) throws Exception {
		release();
		throw new Exception(异常);
	}
	public void close() {
		is运行 = false;
		release();
	}

	public void release() {
		if (packet != null)
			// 手动释放内存
			av_packet_unref(packet);// Free the packet that was allocated by av_read_frame
		// av_free(buffer);// buffer实现了自动关闭接口 可以不管它
		if (视频RGB != null)
			av_free(视频RGB);// Free the RGB image
		if (视频frame != null)
			av_free(视频frame);// Free the YUV frame

		if (音频packet != null)
			// 手动释放内存
			av_packet_unref(音频packet);
		if (音频frame != null)
			av_free(音频frame);

		if (sws_ctx != null)
			sws_freeContext(sws_ctx);// Free SwsContext
		// 关闭解码器

		if (视频编码器 != null) {
			avcodec_free_context(视频编码器);
			avcodec_close(视频编码器);// Close the codec
		}
		if (音频解码器 != null) {
			avcodec_free_context(音频解码器);
			avcodec_close(音频解码器);// Close the codec
		}
		if (输入引擎 != null) {
			// 关闭图像源
			avformat_close_input(输入引擎);// Close the video file
		}
	}



}
